#include "network.h"
#include "gui.h"
#include "map.h"



Client::Client( QObject* parent ): QTcpSocket( parent )
{
	block_size = 0;
	connect( this, SIGNAL( connected() ), this, SLOT( slot_connected() ) );
	connect( this, SIGNAL( stateChanged( QAbstractSocket::SocketState ) ), this, SLOT( status_changed( QAbstractSocket::SocketState ) ) );
	connect( this, SIGNAL( error( QAbstractSocket::SocketError ) ), this, SLOT( connection_error( QAbstractSocket::SocketError ) ) );
	connect( this, SIGNAL( readyRead() ), this, SLOT( receive_message() ) );
};



Client::~Client()
{
	//this->disconnectFromHost();
};



string Client::read_string()
{
	QString noth( "" );
	QDataStream in( this );
	in.setVersion( QDataStream::Qt_4_4 );
	if ( block_size == 0 )
	{
		if ( bytesAvailable() < ( int )sizeof( quint16 ) )
			return noth.toStdString();
		in >> block_size;
	}
	if ( bytesAvailable() < block_size )
		return noth.toStdString();
	QString s;
	in >> s;
	block_size = 0;
	return s.toStdString();
};



void Client::send_string( const string& s )
{
	QString qs( s.c_str() );
	QByteArray block;
	QDataStream out( &block, QIODevice::WriteOnly );
	out.setVersion( QDataStream::Qt_4_4 );
	out << ( quint16 )0;
	out << qs;
	out.device()->seek( 0 );
	out << ( quint16 )( block.size() - sizeof( quint16 ) );
	write( block );
	while ( flush() );
};



void Client::receive_message()
{
	string message;
	for ( ; ; )
	{
		message = read_string();
		if ( message == "" )
			return;
		istringstream mess( message );
		string type;
		string msg;
		getline( mess, type, '=' );
		getline( mess, msg, '=' );

		if ( type == "ACCEPTED" )
		{
			istringstream acc( msg );
			unsigned int my_id;
			acc >> my_id;
			if ( acc.fail() )
			{
				game_gui::get_instance()->show_network_error( "Error while receiving accept message." );
				return;
			}
			network_client::get_instance()->set_net_id( my_id );
			send_string( network_client::get_instance()->get_player() );
			return;
		}

		else if ( type == "SET_PLAYER" )
		{
			istringstream pls( msg );
			//PLAYER_NAME PLAYER_TYPE ( 2 -> network, 1 -> ai, 0 -> local )
			pls.ignore( msg.size(), '"' );
			string pl_name;
			getline( pls, pl_name, '"' );
			unsigned int pl_typ_i;
			unsigned int net_id;
			pls >> pl_typ_i;
			pls >> net_id;
			if ( pls.fail() )
			{
				game_gui::get_instance()->show_network_error( "Error while receiving player information." );
				send_string( "ERROR=Error while receiving player information." );
				return;
			}
			PlayerType pl_typ = local;
			if ( net_id != network_client::get_instance()->get_net_id() )
				pl_typ_i = 2;
			switch ( pl_typ_i )
			{
			case 0:
				pl_typ = local;
				break;
			case 1:
				pl_typ = ai;
				break;
			case 2:
				pl_typ = network;
				break;
			}			
			players* pla = players::get_instance();
			player* pl = new player( pla->get_players_count(), pl_name, pl_typ );
			pla->set_player( pl, pla->get_players_count() );
			//set algorithm
			if ( pl_typ == ai )
			{
				pl->ai_algorithm = ai_algorithms::get_instance()->algorithm_list[ network_client::get_instance()->get_players_algorithm() ]->clone();
				pl->ai_algorithm->init( pl );
			}
			send_string( "OK=3" );
		}

		else if ( type == "MAP" )
		{
			istringstream maps( msg );
			if ( mapa::get_instance()->load( maps ) )
			{
				cout << "MAP RECEIVED SUCCESSFULLY" << endl;
				send_string( "OK=0" );
			}
			else
			{
				game_gui::get_instance()->show_network_error( "Map receiving failed." );
				send_string( "ERROR=Map receiving failed." );
			}
		}

		else if ( type == "OBJECTS" )
		{
			istringstream objs( msg );
			if ( mapa::get_instance()->load_objects( objs ) )
			{
				cout << "OBJECTS RECEIVED SUCCESSFULLY" << endl;
				send_string( "OK=1" );
			}
			else
			{
				game_gui::get_instance()->show_network_error( "Objects receiving failed." );
				send_string( "ERROR=Objects receiving failed." );
			}
		}

		else if ( type == "START_BUY" )
		{
			game::get_instance()->set_network_game( true );
			cout << "GAME STARTED" << endl;
			connect( this, SIGNAL( send_command( player*, const string& ) ), game::get_instance(), SLOT( command_received( player*, const string& ) ) );
			game_gui::get_instance()->start_buy();
		}

		else if ( type == "ACTION" )
		{
			istringstream act( msg );
			string plr_id_s;
			string act_s;
			getline( act, plr_id_s, ':' );
			getline( act, act_s, ':' );
			istringstream plr_id( plr_id_s );
			unsigned int i;
			plr_id >> i;
			emit send_command( players::get_instance()->get_player( i ), act_s );
		}

		else if ( type == "ERROR" )
		{
			game_gui::get_instance()->show_network_error( msg );
		}
	}
};



void Client::status_changed( const QAbstractSocket::SocketState& state )
{
	string s;
	switch( state )
	{
	case QAbstractSocket::UnconnectedState:
		s = "The socket is not connected.";
		break;
	case QAbstractSocket::HostLookupState:
		s = "The socket is performing a host name lookup.";
		break;
	case QAbstractSocket::ConnectingState:
		s = "The socket has started establishing a connection.";
		break;
	case QAbstractSocket::ConnectedState:
		s = "A connection is established.";
		break;
	case QAbstractSocket::BoundState:
		s = "The socket is bound to an address and port (for servers).";
		break;
	case QAbstractSocket::ListeningState:
		s = "For internal use only.";
		break;
	case QAbstractSocket::ClosingState:
		s = "The socket is about to close (data may still be waiting to be written).";
		break;
	}
	game_gui::get_instance()->show_network_status( s );
};



void Client::connection_error( const QAbstractSocket::SocketError& error )
{
	string s;
	switch ( error )
	{
	case QAbstractSocket::ConnectionRefusedError:
		s = "The connection was refused by the peer (or timed out).";
		break;
	case QAbstractSocket::RemoteHostClosedError:
		s = "The remote host closed the connection. Note that the client socket (i.e., this socket) will be closed after the remote close notification has been sent.";
		break;
	case QAbstractSocket::HostNotFoundError:
		s = "The host address was not found.";
		break;
	case QAbstractSocket::SocketAccessError:
		s = "The socket operation failed because the application lacked the required privileges.";
		break;
	case QAbstractSocket::SocketResourceError:
		s = "The local system ran out of resources (e.g., too many sockets).";
		break;
	case QAbstractSocket::SocketTimeoutError:
		s = "The socket operation timed out.";
		break;
	case QAbstractSocket::DatagramTooLargeError:
		s = "The datagram was larger than the operating system's limit (which can be as low as 8192 bytes).";
		break;
	case QAbstractSocket::NetworkError:
		s = "An error occurred with the network (e.g., the network cable was accidentally plugged out).";
		break;
	case QAbstractSocket::AddressInUseError:
		s = "The address specified to QUdpSocket::bind() is already in use and was set to be exclusive.";
		break;
	case QAbstractSocket::SocketAddressNotAvailableError:
		s = "The address specified to QUdpSocket::bind() does not belong to the host.";
		break;
	case QAbstractSocket::UnsupportedSocketOperationError:
		s = "The requested socket operation is not supported by the local operating system (e.g., lack of IPv6 support).";
		break;
	case QAbstractSocket::ProxyAuthenticationRequiredError:
		s = "The socket is using a proxy, and the proxy requires authentication.";
		break;
	case QAbstractSocket::SslHandshakeFailedError:
		s = "The SSL/TLS handshake failed, so the connection was closed (only used in QSslSocket).";
		break;
	case QAbstractSocket::UnfinishedSocketOperationError:
		s = "Used by QQAbstractSocketEngine only, The last operation attempted has not finished yet (still in progress in the background).";
		break;
	case QAbstractSocket::ProxyConnectionRefusedError:
		s = "Could not contact the proxy server because the connection to that server was denied.";
		break;
	case QAbstractSocket::ProxyConnectionClosedError:
		s = "The connection to the proxy server was closed unexpectedly (before the connection to the final peer was established).";
		break;
	case QAbstractSocket::ProxyConnectionTimeoutError:
		s = "The connection to the proxy server timed out or the proxy server stopped responding in the authentication phase.";
		break;
	case QAbstractSocket::ProxyNotFoundError:
		s = "The proxy address set with setProxy() (or the application proxy) was not found.";
		break;
	case QAbstractSocket::ProxyProtocolError:
		s = "The connection negotiation with the proxy server because the response from the proxy server could not be understood.";
		break;
	case QAbstractSocket::UnknownSocketError:
		s = "An unidentified error occurred.";
		break;
	}
	game_gui::get_instance()->show_network_error( s );
};



void Client::slot_connected()
{
	cout << "CONNECTION TO SERVER ESTABLISHED" << endl;
};



network_client* network_client::get_instance()
{
	static network_client inst( 0 );
	return &inst;
};



void network_client::set_player( const string& name, unsigned int typ )
{
	ostringstream os;
	os << "SET_PLAYER=\"" << name << "\" " << typ;
	player_str = os.str();
};



void network_client::set_players_algorithm( const string& alg_name )
{
	player_algorithm = alg_name;
};



string network_client::get_player()
{
	return player_str;
};



string network_client::get_players_algorithm()
{
	return player_algorithm;
};



void network_client::connect_server( const string& address, int port )
{
	client->connectToHost( QString( address.c_str() ), ( quint16 )port );
};


void network_client::deliver_command( player* pl, const string& cmd )
{
	ostringstream os;
	os << "ACTION=" << pl->get_id() << ":" << cmd;
	client->send_string( os.str() );
};